/*
* Copyright (c) 2003- michael lawley and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser General Public License version 2.1 as published by the Free Software Foundation
* which accompanies this distribution, and is available by writing to
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
* Contributors:
* michael lawley
*
*
*
*/
package tefkat.engine;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.eclipse.emf.ecore.EEnum;
import org.eclipse.emf.ecore.EEnumLiteral;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature;
import tefkat.data.DataMap;
import tefkat.model.*;
import tefkat.model.internal.ModelUtils;
/**
* Evaluator implements an expression evaluator for the QVTModel.
* It requires that all Vars referenced in the Expression have been
* supplied with (ground) bindings in the supplied context.
*
* @author lawley
*
*/
public class Evaluator {
private static final String NULL_TYPE = "null";
private final class MinCardinality extends Function2 {
public Object call(Context context, Binding binding, Object[] params) throws ResolutionException, NotGroundException {
if (params.length != 4) {
throw new ResolutionException(null, "min_cardinality expected 4 args, got " + params.length);
}
System.err.println("Assert " + params[0] + "." + params[1] + " = " + params[2] + " >= " + params[3]);
return Boolean.TRUE;
}
}
private final class DataMapLookup extends Function2 {
public Object call(Context context, Binding binding, Object[] params) throws ResolutionException {
DataMap dataMap = (DataMap) params[0];
String key = String.valueOf(params[1]);
Object result = dataMap.getValue().get(key);
if (result instanceof Expression) {
try {
List vals = eval(context, binding, (Expression) result);
if (vals.size() == 1) {
result = vals.get(0);
} else {
result = vals;
}
} catch (ResolutionException e) {
throw new ResolutionException(null, "Map expression '" + result + "' evaluation failed", e);
} catch (NotGroundException e) {
throw new ResolutionException(null, "Map expression '" + result + "' should not contain variable(s)", e);
}
}
return result;
}
}
/**
* Takes a collection and a feature and returns a collection of the results
* of fetching the feature from each of the input objects.
*
* @author michaellawley
*/
private final class MapFeature implements Function {
public Object call(Context context, Object[] params) throws ResolutionException {
final Collection list = (Collection) params[0];
final String feature = (String) params[1];
final List items = new ArrayList();
for (final Iterator itr = list.iterator(); itr.hasNext(); ) {
Object obj = itr.next();
items.add(context.fetchFeature(feature, obj));
}
final ArrayList result = new ArrayList();
result.add(items);
return result;
}
}
private static final class IdentityFunction implements Function {
public Object call(Context context, Object[] params) {
return params[0];
}
}
private static final class CollectFunction implements Function {
public Object call(Context context, Object[] params) {
return Arrays.asList(params);
}
}
private static final class AppendFunction extends Function2 {
public Object call(Context context, Binding binding, Object[] params) throws ResolutionException, NotGroundException {
StringBuffer sb = new StringBuffer();
for (int i = 0; i < params.length; i++) {
if (params[i] instanceof WrappedVar) {
context.delay("Cannot append unbound variable: " + params[i]);
}
sb.append(params[i]);
}
return sb.toString();
}
}
private static final class ElementAt implements Function {
public Object call(Context context, Object[] params) {
List list = (List) params[0];
Number index = (Number) params[1];
return list.get(index.intValue());
}
}
private static final class SubList implements Function {
public Object call(Context context, Object[] params) {
List list = (List) params[0];
Number start = (Number) params[1];
if (params.length > 2) {
Number end = (Number) params[2];
return list.subList(start.intValue(), end.intValue());
} else {
return list.subList(start.intValue(), list.size());
}
}
}
private static final class JoinStrings implements Function {
/**
* concatenate a list of Strings interspersed with a separator
* @params[0] separator
* @params[1..n] the list of strings
*/
public Object call(Context context, Object[] params) {
String separator = String.valueOf(params[0]);
StringBuffer b = new StringBuffer();
if (params.length == 2 && params[1] instanceof Collection) {
for (Iterator itr = ((Collection) params[1]).iterator(); itr.hasNext(); ) {
b.append(itr.next());
if (itr.hasNext()) {
b.append(separator);
}
}
} else if (params.length > 1) {
b.append(params[1]);
for (int i = 2; i < params.length; i++) {
b.append(separator).append(params[i]);
}
}
return b.toString();
}
}
private static final class SplitString implements Function {
public Object call(Context context, Object[] params) {
String string = String.valueOf(params[0]);
String regex = String.valueOf(params[1]);
return Arrays.asList(string.split(regex));
}
}
private static final class StripSuffix implements Function {
public Object call(Context context, Object[] params) {
String str = (String) params[0];
String suffix = (String) params[1];
String result = str;
if (str.endsWith(suffix)) {
result = str.substring(0, str.length() - suffix.length());
}
return result;
}
}
private static final class CastInt implements Function {
public Object call(Context context, Object[] params) {
return Integer.decode(String.valueOf(params[0]));
}
}
private static final class CastLong implements Function {
public Object call(Context context, Object[] params) {
return Long.decode(String.valueOf(params[0]));
}
}
private static final class CastFloat implements Function {
public Object call(Context context, Object[] params) {
return Float.valueOf(String.valueOf(params[0]));
}
}
private static final class CastDouble implements Function {
public Object call(Context context, Object[] params) {
return Double.valueOf(String.valueOf(params[0]));
}
}
private static final class Add implements Function {
public Object call(Context context, Object[] params) {
Number lhs = (Number) params[0];
Number rhs = (Number) params[1];
if (lhs instanceof Float || rhs instanceof Float || lhs instanceof Double || rhs instanceof Double) {
double lval = lhs.doubleValue();
double rval = rhs.doubleValue();
double result = lval + rval;
if (result < Float.MAX_VALUE && result > Float.MIN_VALUE) {
return new Float(result);
} else {
return new Double(result);
}
} else {
long lval = lhs.longValue();
long rval = rhs.longValue();
long result = lval + rval;
if (result < Integer.MAX_VALUE && result > Integer.MIN_VALUE) {
return new Integer((int) result);
} else {
return new Long(result);
}
}
}
}
private static final class Subtract implements Function {
public Object call(Context context, Object[] params) {
Number lhs = (Number) params[0];
Number rhs = (Number) params[1];
if (lhs instanceof Float || rhs instanceof Float || lhs instanceof Double || rhs instanceof Double) {
double lval = lhs.doubleValue();
double rval = rhs.doubleValue();
return new Double(lval - rval);
} else {
long lval = lhs.longValue();
long rval = rhs.longValue();
return new Long(lval - rval);
}
}
}
private static final class Multiply implements Function {
public Object call(Context context, Object[] params) {
Number lhs = (Number) params[0];
Number rhs = (Number) params[1];
if (lhs instanceof Float || rhs instanceof Float || lhs instanceof Double || rhs instanceof Double) {
double lval = lhs.doubleValue();
double rval = rhs.doubleValue();
return new Double(lval * rval);
} else {
long lval = lhs.longValue();
long rval = rhs.longValue();
return new Long(lval * rval);
}
}
}
private static final class Divide implements Function {
public Object call(Context context, Object[] params) {
Number lhs = (Number) params[0];
Number rhs = (Number) params[1];
if (lhs instanceof Float || rhs instanceof Float || lhs instanceof Double || rhs instanceof Double) {
double lval = lhs.doubleValue();
double rval = rhs.doubleValue();
return new Double(lval / rval);
} else {
long lval = lhs.longValue();
long rval = rhs.longValue();
return new Long(lval / rval);
}
}
}
private static final class Sum implements Function {
public Object call(Context context, Object[] params) throws ResolutionException {
Number[] collection = (Number[]) params[0];
boolean integral = true;
for (int i = 0; i < collection.length && integral; i++) {
integral |= collection[i] instanceof Double;
}
if (integral) {
long result = 0;
for (int i = 0; i < collection.length && integral; i++) {
result += collection[i].longValue();
}
return result;
} else {
double result = 0;
for (int i = 0; i < collection.length && integral; i++) {
result += collection[i].doubleValue();
}
return result;
}
}
}
private static final class Foldl implements Function {
public Object call(Context context, Object[] params) throws ResolutionException {
final String function = (String) params[0];
Object result = params[1];
final Collection collection = (Collection) params[2];
final Function func = context.getFunction(function);
for (Object param: collection) {
Object[] args = {result, param};
result = func.call(context, args);
}
return result;
}
}
/**
* Key for Node instance in the funcMap (yes, it's a hack)
*/
final Map funcMap = new HashMap();
private final RuleEvaluator ruleEval;
Evaluator(RuleEvaluator evaluator) {
ruleEval = evaluator;
initFunctionMap();
}
private void initFunctionMap() {
addFunction("identity", new IdentityFunction());
addFunction("collect", new CollectFunction());
addFunction("append", new AppendFunction());
addFunction("elementAt", new ElementAt());
addFunction("subList", new SubList());
addFunction("join", new JoinStrings());
addFunction("split", new SplitString());
addFunction("stripSuffix", new StripSuffix());
addFunction("int", new CastInt());
addFunction("long", new CastLong());
addFunction("float", new CastFloat());
addFunction("double", new CastDouble());
addFunction("sum", new Sum());
addFunction("+", new Add());
addFunction("-", new Subtract());
addFunction("*", new Multiply());
addFunction("/", new Divide());
addFunction("funmap", new MapFeature());
addFunction("foldl", new Foldl());
// FIXME rename this function to dataMap or something (see tefkat.g)
addFunction("map", new DataMapLookup());
addFunction("min_cardinality", new MinCardinality());
}
final void addFunction(String name, Function function) {
if (funcMap.containsKey(name)) {
throw new IllegalArgumentException("A Function with named " + name + " is already registered.");
}
funcMap.put(name, function);
}
/**
* Evaluate expr given the bindings in node.
*
* @param node The Term context for the evaluation (supplies initial bindings)
* @param expr The Expression to evaluate
* @return A List of the values of the Expression. VarUse of an unbound Var evaluates to a WrappedVar.
* @throws ResolutionException
*/
List eval(Context context, Expression expr)
throws ResolutionException, NotGroundException {
try {
ExtentUtil.highlightNode(expr, ExtentUtil.TERM_ENTER);
List result = doEval(context, context.getBindings(), expr);
ExtentUtil.highlightNode(expr, ExtentUtil.TERM_EXIT);
return result;
} catch (NotGroundException e) {
ExtentUtil.highlightNode(expr, ExtentUtil.TERM_DELAY);
throw e;
}
}
private List eval(Context context, Binding binding, Expression expr)
throws ResolutionException, NotGroundException {
try {
ExtentUtil.highlightNode(expr, ExtentUtil.TERM_ENTER);
List result = doEval(context, binding, expr);
ExtentUtil.highlightNode(expr, ExtentUtil.TERM_EXIT);
return result;
} catch (NotGroundException e) {
ExtentUtil.highlightNode(expr, ExtentUtil.TERM_DELAY);
throw e;
}
}
/**
* Return the list of instances that wVar should be bound to.
*
* @param context
* @param wVar
* @return
* @throws NotGroundException
*/
List expand(Context context, WrappedVar wVar) throws NotGroundException {
if (null == wVar.getExtent()) {
context.delay("Unsupported mode: unbound extent for " + wVar);
}
// Var var = wVar.getVar();
// EClassifier type = wVar.getType();
// System.out.println("Expanding " + wVar); // TODO delete
// System.out.println("\t" + wVar.getExtent().getObjectsByClass(wVar.getType(), wVar.isExact()));
return wVar.getExtent().getObjectsByClass(wVar.getType(), wVar.isExact());
}
private List doEval(Context context, Binding binding, Expression expr)
throws ResolutionException, NotGroundException {
List values;
if (expr instanceof SimpleExpr) {
values = new ArrayList();
values.add(evalSimpleExpr(expr));
} else if (expr instanceof EnumConstant) {
values = evalEnumExpr(context, binding, expr);
} else if (expr instanceof VarUse) {
values = new ArrayList();
Var var = ((VarUse) expr).getVar();
Object value = binding.lookup(var);
if (null == value) {
values.add(new WrappedVar(var));
} else {
values.add(value);
}
} else if (expr instanceof FeatureExpr) {
values = evalFeatureExpr(context, binding, expr);
} else if (expr instanceof FunctionExpr) {
values = evalFunctionExpr(context, binding, expr);
} else if (expr instanceof InstanceRef) {
values = new ArrayList();
InstanceRef ref = (InstanceRef) expr;
values.add(ref.getObject());
} else if (expr instanceof CollectionExpr) {
values = evalCollectionExpr(context, binding, expr);
} else {
throw new ResolutionException(
null,
expr.eClass().getName() + " Expressions are not yet supported: " + expr);
}
return values;
}
private List evalCollectionExpr(Context context, Binding binding, Expression expr) throws ResolutionException, NotGroundException {
// FIXME - Yikes, the semantiocs of these is complicated
//
// Should X = [1, Y.foo] where Y.foo returns the collection [2, 3]
// bind X to 1, 2, and 3 or to [1, 2, 3] or to [1, 2] and [1, 3] ?
//
// What about X = [1, Y.foo{}]?
//
// For the moment, we'll make X = [expr_1, ..., expr_k] equivalent
// to X = union(expr_1, ..., expr_k) i.e., for the example above,
// X would bind to 1, then 2, then 3.
//
CollectionExpr collection = (CollectionExpr) expr;
List args = collection.getArg();
Function f = (Function) funcMap.get("collect");
ExprExpander expander = new ExprExpander(context, f, binding, args, true);
List results = expander.getResults();
return results;
}
private List evalFunctionExpr(Context context, Binding binding, Expression expr) throws ResolutionException, NotGroundException {
List values;
FunctionExpr funcExpr = (FunctionExpr) expr;
String op = funcExpr.getFunction();
List args = funcExpr.getArg();
Function f = (Function) funcMap.get(op);
try {
if (null != f) {
if (args.size() > 0) {
ExprExpander expander = new ExprExpander(context, f, binding, args, false);
values = expander.getResults();
} else {
values = new ArrayList();
if (f instanceof Function2) {
values.add(((Function2) f).call(context, binding, args.toArray()));
} else {
values.add(f.call(context, args.toArray()));
}
}
} else if ("collect".equals(op)) {
f = (Function) funcMap.get("collect");
ExprExpander expander = new ExprExpander(context, f, binding, args, true);
values = expander.getResults();
} else {
throw new ResolutionException(null, "Unknown function: " + op);
}
} catch (ClassCastException e) {
e.printStackTrace();
throw new ResolutionException(null, "Badly typed parameter(s) to function: " + op, e);
} catch (RuntimeException e) {
throw new ResolutionException(null, "Function evaluation failed: " + op, e);
}
return values;
}
private List evalFeatureExpr(Context context, Binding binding, Expression expr)
throws ResolutionException, NotGroundException {
List values = new ArrayList();
FeatureExpr featExpr = (FeatureExpr) expr;
Collection featureNames = eval(context, binding, featExpr.getFeature());
List args = featExpr.getArg();
List objs = eval(context, binding, (Expression) args.get(0));
for (final Iterator fItr = featureNames.iterator(); fItr.hasNext(); ) {
Object fObj = fItr.next();
Binding featureContext = null;
if (fObj instanceof WrappedVar) {
Var var = ((WrappedVar) fObj).getVar();
context.delay("Unsupported mode (unbound '" + var.getName() + "') for FeatureExpr: " + var.getName() + "." + featExpr.getFeature());
} else if (fObj instanceof BindingPair) {
featureContext = (Binding) fObj;
fObj = ((BindingPair) fObj).getValue();
}
if (fObj instanceof EStructuralFeature) {
// TODO FIXME this is a HACK
fObj = ((EStructuralFeature) fObj).getName();
} else if (!(fObj instanceof String)) {
throw new ResolutionException(null, "The Feature Expression " + featExpr + " must evaluate to a feature name of type String, not " + fObj.getClass());
}
String featureName = (String) fObj;
// Expand a typed variable if possible
//
Var var = null;
if (objs.size() == 1 && objs.get(0) instanceof WrappedVar) {
WrappedVar wVar = (WrappedVar) objs.get(0);
objs = expand(context, wVar);
var = wVar.getVar();
}
for (final Iterator objItr = objs.iterator(); objItr.hasNext(); ) {
Object obj = objItr.next();
Binding objectContext = featureContext;
if (obj instanceof BindingPair) {
if (null == objectContext) {
objectContext = (Binding) obj;
} else {
objectContext = new Binding(objectContext);
objectContext.composeLeft((Binding) obj);
}
obj = ((BindingPair) obj).getValue();
}
// System.err.println("TGT OBJ: " + obj + "\t" + obj.getClass());
// If we're transforming a Transformation, then we will bind to
// AbstractVars so we need to wrap our AbstractVars to so that we
// can distinguish unbound _variables_ from _objects_.
if (featExpr.isOperation()) {
Collection valuesObject = callOperation(context, binding, featureName, obj, args.subList(1, args.size()), featExpr.isCollect());
if (null != var) {
for (final Iterator itr = valuesObject.iterator(); itr.hasNext(); ) {
Object val = itr.next();
Binding unifier = new BindingPair(objectContext, val);
unifier.add(var, obj);
values.add(unifier);
}
} else if (null != objectContext) {
for (final Iterator itr = valuesObject.iterator(); itr.hasNext(); ) {
Object val = itr.next();
Binding unifier = new BindingPair(objectContext, val);
values.add(unifier);
}
} else {
values.addAll(valuesObject);
}
} else {
Object valuesObject = context.fetchFeature(featureName, obj);
if (null != valuesObject && valuesObject.getClass().isArray()) {
valuesObject = wrapArray(valuesObject);
}
if (valuesObject instanceof Collection) {
if (featExpr.isCollect()) {
if (null != var) {
Binding unifier = new BindingPair(objectContext, valuesObject);
unifier.add(var, obj);
values.add(unifier);
} else if (null != objectContext) {
Binding unifier = new BindingPair(objectContext, valuesObject);
values.add(unifier);
} else {
values.add(valuesObject);
}
} else {
if (null != var) {
for (final Iterator itr = ((Collection) valuesObject).iterator(); itr.hasNext(); ) {
Object val = itr.next();
Binding unifier = new BindingPair(objectContext, val);
unifier.add(var, obj);
values.add(unifier);
}
} else if (null != objectContext) {
for (final Iterator itr = ((Collection) valuesObject).iterator(); itr.hasNext(); ) {
Object val = itr.next();
Binding unifier = new BindingPair(objectContext, val);
values.add(unifier);
}
} else {
values.addAll((Collection) valuesObject);
}
}
} else if (null != valuesObject) {
if (featExpr.isCollect()) {
List l = new ArrayList(1);
l.add(valuesObject);
if (null != var) {
Binding unifier = new BindingPair(objectContext, l);
unifier.add(var, obj);
values.add(unifier);
} else if (null != objectContext) {
Binding unifier = new BindingPair(objectContext, l);
values.add(unifier);
} else {
values.add(l);
}
} else {
if (null != var) {
Binding unifier = new BindingPair(objectContext, valuesObject);
unifier.add(var, obj);
values.add(unifier);
} else if (null != objectContext) {
Binding unifier = new BindingPair(objectContext, valuesObject);
values.add(unifier);
} else {
values.add(valuesObject);
}
}
}
}
}
}
return values;
}
/**
* Turn an array of things into a List of things, boxing primitive types as required.
*
* @param valuesObject
* @return
*/
private Object wrapArray(Object valuesObject) {
if (valuesObject instanceof Object[]) {
valuesObject = Arrays.asList((Object[]) valuesObject);
} else if (valuesObject.getClass().isArray()) {
final int length = Array.getLength(valuesObject);
List l = new ArrayList(length);
for (int i = 0; i < length; i++) {
l.add(Array.get(valuesObject, i));
}
valuesObject = l;
}
return valuesObject;
}
private Object evalSimpleExpr(Expression expr) {
Object value;
if (expr instanceof StringConstant) {
value = ((SimpleExpr) expr).getRepresentation();
} else if (expr instanceof BooleanConstant) {
value = Boolean.valueOf(((SimpleExpr) expr).getRepresentation());
} else if (expr instanceof IntConstant) {
value = Integer.valueOf(((SimpleExpr) expr).getRepresentation());
} else if (expr instanceof RealConstant) {
value = Double.valueOf(((SimpleExpr) expr).getRepresentation());
} else {
throw new IllegalArgumentException(
"Unsupported expression type: " + expr);
}
return value;
}
private List evalEnumExpr(Context context, Binding binding, Expression expr)
throws ResolutionException, NotGroundException {
List values = new ArrayList();
List args = ((EnumConstant) expr).getArg();
Expression enumExpr = (Expression) args.get(0);
Expression literalExpr = (Expression) args.get(1);
List enumObjs = eval(context, binding, enumExpr);
List literalObjs = eval(context, binding, literalExpr);
for (final Iterator eItr = enumObjs.iterator(); eItr.hasNext(); ) {
Object eObj = eItr.next();
EEnum enumeration = null;
if (eObj instanceof String) {
eObj = ModelUtils.findClassifierByName(ruleEval.nameMap, (String) eObj);
}
if (eObj instanceof EEnum) {
enumeration = (EEnum) eObj;
}
if (null != enumeration) {
for (final Iterator lItr = literalObjs.iterator(); lItr.hasNext(); ) {
Object lObj = lItr.next();
if (lObj instanceof String) {
EEnumLiteral eLit = enumeration.getEEnumLiteral((String) lObj);
if (null != eLit) {
values.add(eLit.getInstance());
}
}
}
}
}
return values;
}
/**
* @param unifier
* @param args
* @return
* @throws ResolutionException
* @throws NotGroundException
*/
//
// The change below and the expandParams functions appear to be addressing a different
// albeit related problem to the rest of the changes to Tree, TreeListener, *Resolver etc
// Those changes are dealing with delays propagating out of sub-Trees -- see the FIXME below
void evalAll(Context context, Binding unifier, List args, Function function) throws ResolutionException, NotGroundException {
new ExprExpander(context, function, unifier, args, false);
}
final private Map methodCache = new HashMap();
private Method resolveMethod(Object instance, String name, Object[] params) {
Map methodCache = getMethodCache(instance, name);
Method method = null;
// Deal with zero-arity method first
if (null == params || params.length == 0) {
Class cls = instance.getClass();
method = (Method) methodCache.get(NULL_TYPE);
if (null == method) {
try {
Method[] ms = cls.getMethods();
for (int i = 0; null == method && i < ms.length; i++) {
if (name.equals(ms[i].getName()) && ms[i].getParameterTypes().length == 0) {
method = ms[i];
}
}
} catch (SecurityException e) {
}
}
if (null != method) {
// Cache the result
methodCache.put(NULL_TYPE, method);
}
return method;
}
Class[] rawTypes = new Class[params.length];
boolean hasBoxedTypes = false;
for (int j = 0; j < params.length; j++) {
Class type = params[j].getClass();
rawTypes[j] = type;
if (Number.class.isAssignableFrom(type)) {
hasBoxedTypes = true;
}
}
method = (Method) methodCache.get(Arrays.asList(rawTypes));
if (null == method) {
Class[] unboxedTypes = null;
if (hasBoxedTypes) {
unboxedTypes = new Class[params.length];
for (int j = 0; j < params.length; j++) {
Class type = params[j].getClass();
rawTypes[j] = type;
if (Number.class.isAssignableFrom(type)) {
try {
Field typeField = type.getField("TYPE");
unboxedTypes[j] = (Class) typeField.get(type);
} catch (SecurityException e) {
} catch (NoSuchFieldException e) {
} catch (IllegalArgumentException e) {
} catch (IllegalAccessException e) {
}
} else {
unboxedTypes[j] = type;
}
}
}
method = resolveMethod(instance, name, rawTypes, unboxedTypes);
if (null != method) {
// Cache the result
methodCache.put(Arrays.asList(rawTypes), method);
}
}
return method;
}
/**
* Beware, this will find the first method (they are in an arbitrary order) that matches subject to
* auto-unboxing...eg, the choice between foo(int) and foo(Integer) is arbitrary
*
* @param instance
* @param name
* @param types
* @param unboxedTypes may be null, but otherwise the same length as types
* @return
*/
private Method resolveMethod(Object instance, String name, Class[] types, Class[] unboxedTypes) {
Method method = null;
Class cls = instance.getClass();
// FIXME: This will fail for private Classes implementing public Interfaces
Method[] ms = cls.getMethods();
for (int i = 0; null == method && i < ms.length; i++) {
if (name.equals(ms[i].getName())) {
Class[] parameterTypes = ms[i].getParameterTypes();
if (parameterTypes.length != types.length) {
continue;
}
method = ms[i];
for (int j = 0; j < parameterTypes.length; j++) {
if (!parameterTypes[j].isAssignableFrom(types[j]) &&
(null == unboxedTypes || !parameterTypes[j].isAssignableFrom(unboxedTypes[j]))) {
method = null;
break;
}
}
}
}
return method;
}
private Map getMethodCache(Object instance, String name) {
Map namesToTypes = (Map) methodCache.get(instance.getClass());
Map typesToMethods;
if (null != namesToTypes) {
typesToMethods = (Map) namesToTypes.get(name);
if (null == typesToMethods) {
typesToMethods = new HashMap();
namesToTypes.put(name, typesToMethods);
}
} else {
namesToTypes = new HashMap();
typesToMethods = new HashMap();
methodCache.put(instance.getClass(), namesToTypes);
namesToTypes.put(name, typesToMethods);
}
return typesToMethods;
}
private void warnNoMethod(Object instance, String methodName) {
String message = "No such operation: ";
if (instance instanceof EObject) {
message += ModelUtils.getFullyQualifiedName(((EObject) instance).eClass()) + "." + methodName;
} else {
message += ModelUtils.getFullyQualifiedName(instance.getClass()) + "." + methodName;
}
// System.err.println(message);
ruleEval.fireWarning(message);
}
private List callOperation(Context context, Binding binding, final String operationName, final Object instance, List args, boolean collect)
throws ResolutionException, NotGroundException {
Function methodCall = new Function() {
public Object call(Context context, Object[] params) throws ResolutionException {
Object result = null;
// In the general case the param types could all be different and
// thus invoke different methods so we cannot cache/lift this
// method resolution call
Method method = resolveMethod(instance, operationName, params);
if (null == method) {
warnNoMethod(instance, operationName);
result = Collections.EMPTY_LIST;
} else {
try {
result = method.invoke(instance, params);
} catch (SecurityException e) {
ruleEval.fireWarning(e);
} catch (IllegalArgumentException e) {
ruleEval.fireWarning(e);
} catch (IllegalAccessException e) {
ruleEval.fireWarning(e);
} catch (InvocationTargetException e) {
ruleEval.fireWarning(e);
} catch (Exception e) {
e.printStackTrace();
// ruleEval.fireError(e);
throw new ResolutionException(null, "Operation invocation "+ operationName + " failed", e);
}
if (null == result) {
result = Collections.EMPTY_LIST;
}
}
return result;
}
};
List results;
if (args.size() > 0) {
ExprExpander expander = new ExprExpander(context, methodCall, binding, args, collect);
results = expander.getResults();
} else {
results = new ArrayList();
Object result = methodCall.call(context, null);
if (null != result) {
if (!collect && result instanceof Collection) {
results.addAll((Collection) result);
} else {
results.add(result);
}
}
}
// System.out.println("\t" + operationName + "(...) = " + results); // TODO delete
return results;
}
final class ExprExpander {
final private List results = new ArrayList();
final private Context context;
final private Function function;
final private List actuals;
final private Object[] params;
final private boolean collect;
final private boolean allowUnboundParameters;
/**
*
* @param function Function to call for each set of actual parameter values
* @param binding outer Binding context for the calls
* @param actuals List of Expressions to evaluate to obtain the parameter values
* @param collect true means preserve nesting of result values
* @throws NotGroundException
* @throws ResolutionException
*/
ExprExpander(Context context, Function function, Binding binding, List actuals, boolean collect)
throws NotGroundException, ResolutionException {
this.context = context;
this.function = function;
this.actuals = actuals;
this.collect = collect;
this.allowUnboundParameters = function instanceof Function2;
params = new Object[actuals.size()];
expandParams(binding, 0);
}
private void expandParams(Binding binding, int i)
throws NotGroundException, ResolutionException {
if (i == params.length) {
Object result = (function instanceof Function2)
? ((Function2) function).call(context, binding, params)
: function.call(context, params);
if (null != result) {
if (!collect && result instanceof Collection) {
results.addAll((Collection) result);
} else {
results.add(result);
}
} else {
ruleEval.fireWarning("Function call returned null: " + function + "(" + params + ")");
}
} else {
List values = eval(context, binding, (Expression) actuals.get(i));
for (final Iterator itr = values.iterator(); itr.hasNext(); ) {
Object obj = itr.next();
Binding newContext;
if (obj instanceof BindingPair) {
newContext = new Binding(binding);
newContext.composeRight((BindingPair) obj);
obj = ((BindingPair) obj).getValue();
System.err.println(params[i] + " = " + obj);
System.err.println(binding);
System.err.println(newContext);
} else if (obj instanceof WrappedVar && !allowUnboundParameters) {
context.delay("Unbound var for Function call not allowed");
newContext = null; // NOTREACHED
} else {
newContext = binding;
}
params[i] = obj;
expandParams(newContext, i+1);
}
}
}
List getResults() {
return results;
}
}
}